﻿using System;
using System.Collections.Generic;

namespace Kyoh.Lib.Collections
{
    /// <summary>インデックスとキーの両方でアクセス可能な <see cref="IDictionary{TKey, TValue}"/> インターフェースの実装。</summary>
    /// <typeparam name="TKey">キーの型。<see cref="System.Int32"/> 以外を指定できます。</typeparam>
    /// <typeparam name="TValue">値の型。</typeparam>
    public class ListedDictionary<TKey, TValue> : IListedDictionary<TKey,TValue>
    {
        private readonly Dictionary<TKey, TValue> dictionary;
        private readonly List<KeyValuePair<TKey, TValue>> list = new List<KeyValuePair<TKey, TValue>>();

        /// <summary>ソートに使用する <see cref="IComparer{T}"/> インターフェースを実装したオブジェクトを取得・設定します。</summary>
        public IComparer<KeyValuePair<TKey, TValue>> Sorter { get; set; }
        
        /// <summary><see cref="ListedDictionary{TKey, TValue}"/> クラスの新しいインスタンスを初期化します。</summary>
        public ListedDictionary()
        {
            this.dictionary = new Dictionary<TKey, TValue>();
        }
        /// <summary><see cref="ListedDictionary{TKey, TValue}"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="dictionary">内容を表す <see cref="IDictionary{TKey, TValue}"/> インターフェースを実装したオブジェクト。</param>
        /// <param name="sorter">ソートに使用する <see cref="IComparer{T}"/> インターフェースを実装したオブジェクト。</param>
        public ListedDictionary(IDictionary<TKey, TValue> dictionary, IComparer<KeyValuePair<TKey, TValue>> sorter = null)
        {
            this.dictionary = new Dictionary<TKey, TValue>(dictionary);
            this.Sorter = sorter;
        }
        /// <summary><see cref="ListedDictionary{TKey, TValue}"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="comparer">キーの同値比較に使用する <see cref="IEqualityComparer{TKey}"/> インターフェースを実装したオブジェクト。</param>
        /// <param name="sorter">ソートに使用する <see cref="IComparer{T}"/> インターフェースを実装したオブジェクト。</param>
        public ListedDictionary(IEqualityComparer<TKey> comparer, IComparer<KeyValuePair<TKey, TValue>> sorter = null)
        {
            this.dictionary = new Dictionary<TKey, TValue>(comparer);
            this.Sorter = sorter;
        }
        /// <summary><see cref="ListedDictionary{TKey, TValue}"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="capacity"><see cref="ListedDictionary{TKey, TValue}"/> が格納できる要素数の初期値。</param>
        /// <param name="sorter">ソートに使用する <see cref="IComparer{T}"/> インターフェースを実装したオブジェクト。</param>
        public ListedDictionary(int capacity, IComparer<KeyValuePair<TKey, TValue>> sorter = null)
        {
            this.dictionary = new Dictionary<TKey, TValue>(capacity);
            this.Sorter = sorter;
        }
        /// <summary><see cref="ListedDictionary{TKey, TValue}"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="dictionary">内容を表す <see cref="IDictionary{TKey, TValue}"/> インターフェースを実装したオブジェクト。</param>
        /// <param name="comparer">キーの同値比較に使用する <see cref="IEqualityComparer{TKey}"/> インターフェースを実装したオブジェクト。</param>
        /// <param name="sorter">ソートに使用する <see cref="IComparer{T}"/> インターフェースを実装したオブジェクト。</param>
        public ListedDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer, IComparer<KeyValuePair<TKey, TValue>> sorter = null)
        {
            this.dictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
            this.Sorter = sorter;
        }
        /// <summary><see cref="ListedDictionary{TKey, TValue}"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="capacity"><see cref="ListedDictionary{TKey, TValue}"/> が格納できる要素数の初期値。</param>
        /// <param name="comparer">キーの同値比較に使用する <see cref="IEqualityComparer{TKey}"/> インターフェースを実装したオブジェクト。</param>
        /// <param name="sorter">ソートに使用する <see cref="IComparer{T}"/> インターフェースを実装したオブジェクト。</param>
        public ListedDictionary(int capacity, IEqualityComparer<TKey> comparer, IComparer<KeyValuePair<TKey, TValue>> sorter = null)
        {
            this.dictionary = new Dictionary<TKey, TValue>(capacity, comparer);
            this.Sorter = sorter;
        }

        /// <summary>要素を追加します。</summary>
        /// <param name="key">キー。</param>
        /// <param name="value">値。</param>
        public void Add(TKey key, TValue value)
        {
            this.dictionary.Add(key, value);
            this.list.Add(new KeyValuePair<TKey, TValue>(key, value));
        }

        /// <summary>要素を挿入します。</summary>
        /// <param name="index">挿入する位置を示すオフセット。</param>
        /// <param name="key">キー。</param>
        /// <param name="value">値。</param>
        public void Insert(int index, TKey key, TValue value)
        {
            this.dictionary.Add(key, value);
            this.list.Insert(index, new KeyValuePair<TKey, TValue>(key, value));
        }

        /// <summary>指定されたキーを持つ要素が存在するかどうかを取得します。</summary>
        /// <param name="key">キー。</param>
        /// <returns>指定されたキーを持つ要素が存在する場合は <c>true</c>。それ以外の場合は <c>false</c>。</returns>
        public bool ContainsKey(TKey key)
        {
            return this.dictionary.ContainsKey(key);
        }

        /// <summary>指定された <typeparamref name="TValue"/> オブジェクトを値に持つ要素が存在するかどうかを取得します。</summary>
        /// <param name="item">値。</param>
        /// <returns>指定された <typeparamref name="TValue"/> オブジェクトを値に持つ要素が存在する場合は <c>true</c>。それ以外の場合は <c>false</c>。</returns>
        public bool ContainsValue(TValue item)
        {
            return this.dictionary.ContainsValue(item);
        }

        /// <summary>キーの <see cref="ICollection{T}"/> 型のコレクションを取得します。</summary>
        public ICollection<TKey> Keys
        {
            get { return this.list.ConvertAll(kv => kv.Key).AsReadOnly(); }
        }

        /// <summary>キーを指定して要素を削除します。</summary>
        /// <param name="key">キー。</param>
        /// <returns>指定されたキーが存在し、削除した場合には <c>true</c>。それ以外の場合は <c>false</c>。</returns>
        public bool Remove(TKey key)
        {
            if (this.dictionary.Remove(key))
            {
                int index = this.list.FindIndex(kv => kv.Key.Equals(key));
                this.list.RemoveAt(index);
                return true;
            }
            else
                return false;
        }

        /// <summary>キーを指定して要素を取得します。戻り値は、取得できた場合は <c>true</c> 、取得できなかった場合は <c>false</c> とします。</summary>
        /// <param name="key">キー。</param>
        /// <param name="value">取得できた場合は、取得したオブジェクトを格納する出力パラメータ。</param>
        /// <returns>取得できた場合は <c>true</c> 、取得できなかった場合は <c>false</c> 。</returns>
        public bool TryGetValue(TKey key, out TValue value)
        {
            return this.TryGetValue(key, out value);
        }

        /// <summary>値の <see cref="ICollection{T}"/> 型のコレクションを取得します。</summary>
        public ICollection<TValue> Values
        {
            get { return this.list.ConvertAll(kv => kv.Value).AsReadOnly(); }
        }

        /// <summary>キーを指定して要素を取得します。</summary>
        /// <param name="key">キー。</param>
        /// <returns>取得した <typeparamref name="TValue"/> オブジェクト。</returns>
        public TValue this[TKey key]
        {
            get { return this.dictionary[key]; }
            set
            {
                this.dictionary[key] = value;
                this.list[this.KeyIndexOf(key)] = new KeyValuePair<TKey, TValue>(key, value);
            }
        }

        /// <summary>インデックスを指定してキーと値のペアを取得します。</summary>
        /// <param name="index">インデックス。</param>
        /// <returns>キーと値を持つ <see cref="KeyValuePair{TKey, TValue}"/>。</returns>
        public KeyValuePair<TKey, TValue> this[int index]
        {
            get { return this.list[index]; }
            set
            {
                TValue oldValue = this.dictionary[value.Key];
                this.dictionary[value.Key] = value.Value;
                try { this.list[index] = value; }
                catch
                {
                    this.dictionary[value.Key] = oldValue;
                    throw;
                }
            }
        }

        /// <summary>インデックスを指定して要素の値を設定します。</summary>
        /// <param name="index">インデックス。</param>
        /// <param name="value">設定する新しい <typeparamref name="TValue"/>。</param>
        public void SetValue(int index, TValue value)
        {
            var key = this.list[index].Key;
            this.list[index] = new KeyValuePair<TKey, TValue>(key, value);
            this.dictionary[key] = value;
        }

        /// <summary>インデックスを指定して要素の値を取得します。</summary>
        /// <param name="index">インデックス。</param>
        /// <returns>指定されたインデックスの <typeparamref name="TValue"/>。</returns>
        public TValue GetValue(int index)
        {
            return this.list[index].Value;
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
        {
            ((IDictionary<TKey, TValue>)this.dictionary).Add(item);
            this.list.Add(item);
        }

        /// <summary>要素をすべて削除します。</summary>
        public void Clear()
        {
            this.dictionary.Clear();
            this.list.Clear();
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
        {
            return this.list.Contains(item);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            this.list.CopyTo(array, arrayIndex);
        }

        /// <summary>値の <typeparamref name="TValue"/> オブジェクトをすべて配列にコピーします。</summary>
        /// <param name="value">値をコピーする配列。</param>
        /// <param name="arrayIndex">コピー先の配列のオフセット。</param>
        public void CopyValueTo(TValue[] value, int arrayIndex)
        {
            this.list.ConvertAll(kv => kv.Value).CopyTo(value, arrayIndex);
        }

        /// <summary>要素数を取得します。</summary>
        public int Count
        {
            get { return this.dictionary.Count; }
        }

        /// <summary>読み取り専用かどうかを取得します。</summary>
        public bool IsReadOnly
        {
            get { return false; }
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            if (((IDictionary<TKey, TValue>)this.dictionary).Remove(item))
            {
                this.list.Remove(item);
                return true;
            }
            else
                return false;
        }

        /// <summary>キーと値のペアを列挙します。</summary>
        /// <returns>キーと値のペアを表す <see cref="KeyValuePair{TKey, TValue}"/> オブジェクトを列挙する <see cref="IEnumerator{T}"/></returns>
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return this.list.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.list.GetEnumerator();
        }

        /// <summary>指定された条件を満たすキーをすべて取得します。</summary>
        /// <param name="predicate">抽出条件を表す <see cref="Predicate{T}"/> デリゲート。</param>
        /// <returns>抽出条件を満たすすべてのキーを格納した <see cref="IList{TKey}"/> オブジェクト。</returns>
        public IList<TKey> FindKeyAll(Predicate<TKey> predicate)
        {
            return this.list.FindAll(kv => predicate(kv.Key)).ConvertAll(kv => kv.Key).AsReadOnly();
        }

        /// <summary>指定された条件を満たすキーを取得します。</summary>
        /// <param name="predicate">抽出条件を表す <see cref="Predicate{T}"/> デリゲート。</param>
        /// <returns>抽出条件を満たす <typeparamref name="TKey"/> オブジェクト。</returns>
        public TKey FindKey(Predicate<TKey> predicate)
        {
            return this.list.Find(kv => predicate(kv.Key)).Key;
        }
        
        /// <summary>指定された条件を満たすキーの最初のインデックスを取得します。</summary>
        /// <param name="predicate">抽出条件を表す <see cref="Predicate{TKey}"/> デリゲート。</param>
        /// <returns>抽出条件を満たすキーの最初のインデックスを表す <see cref="Int32"/> 値。</returns>
        public int FindKeyIndex(Predicate<TKey> predicate)
        {
            return this.list.FindIndex(kv => predicate(kv.Key));
        }

        /// <summary>指定された条件を満たすキーの最後のインデックスを取得します。</summary>
        /// <param name="predicate">抽出条件を表す <see cref="Predicate{TKey}"/> デリゲート。</param>
        /// <returns>抽出条件を満たすキーの最後のインデックスを表す <see cref="Int32"/> 値。</returns>
        public int FindKeyLastIndex(Predicate<TKey> predicate)
        {
            return this.list.FindLastIndex(kv => predicate(kv.Key));
        }

        /// <summary>指定された条件を満たす値をすべて取得します。</summary>
        /// <param name="predicate">抽出条件を表す <see cref="Predicate{TValue}"/> デリゲート。</param>
        /// <returns>抽出条件を満たすすべての値を格納した <see cref="IList{TValue}"/> オブジェクト。</returns>
        public IList<TValue> FindValueAll(Predicate<TValue> predicate)
        {
            return this.list.FindAll(kv => predicate(kv.Value)).ConvertAll(kv => kv.Value).AsReadOnly();
        }

        /// <summary>指定された条件を満たす値を取得します。</summary>
        /// <param name="predicate">抽出条件を表す <see cref="Predicate{TValue}"/> デリゲート。</param>
        /// <returns>抽出条件を満たす <typeparamref name="TValue"/> オブジェクト。</returns>
        public TValue FindValue(Predicate<TValue> predicate)
        {
            return this.list.Find(kv => predicate(kv.Value)).Value;
        }

        /// <summary>指定された条件を満たす値の最初のインデックスを取得します。</summary>
        /// <param name="predicate">抽出条件を表す <see cref="Predicate{TValue}"/> デリゲート。</param>
        /// <returns>抽出条件を満たす値の最初のインデックスを表す <see cref="Int32"/> 値。</returns>
        public int FindValueIndex(Predicate<TValue> predicate)
        {
            return this.list.FindIndex(kv => predicate(kv.Value));
        }

        /// <summary>指定された条件を満たす値の最後のインデックスを取得します。</summary>
        /// <param name="predicate">抽出条件を表す <see cref="Predicate{TValue}"/> デリゲート。</param>
        /// <returns>抽出条件を満たす値の最後のインデックスを表す <see cref="Int32"/> 値。</returns>
        public int FindValueLastIndex(Predicate<TValue> predicate)
        {
            return this.list.FindLastIndex(kv => predicate(kv.Value));
        }

        /// <summary>指定されたキーの最初のインデックスを取得します。</summary>
        /// <param name="key">キー。</param>
        /// <returns>指定されたキーの最初のインデックスを表す <see cref="Int32"/> 値。</returns>
        public int KeyIndexOf(TKey key)
        {
            return this.list.FindIndex(kv => kv.Key.Equals(key));
        }

        /// <summary>指定された値の最初のインデックスを取得します。</summary>
        /// <param name="value">値。</param>
        /// <returns>指定された値の最初のインデックスを表す <see cref="Int32"/> 値。</returns>
        public int ValueIndexOf(TValue value)
        {
            return this.list.FindIndex(kv => kv.Value.Equals(value));
        }

        /// <summary>指定された値の最後のインデックスを取得します。</summary>
        /// <param name="value">値。</param>
        /// <returns>指定された値の最後のインデックスを表す <see cref="Int32"/> 値。</returns>
        public int ValueLastIndexOf(TValue value)
        {
            return this.list.FindLastIndex(kv => kv.Value.Equals(value));
        }

        /// <summary>指定されたインデックスの要素を削除します。</summary>
        /// <param name="index">削除するインデックス。</param>
        public void RemoveAt(int index)
        {
            this.Remove(this.list[index].Key);
        }

        /// <summary>要素をソートします。</summary>
        /// <param name="comparison">要素の比較に使用する <see cref="Comparison{T}"/> デリゲート。</param>
        public void Sort(Comparison<KeyValuePair<TKey, TValue>> comparison)
        {
            this.list.Sort(comparison);
        }

        /// <summary>要素をソートします。</summary>
        /// <param name="comparer">要素の比較に使用する <see cref="IComparer{T}"/> インターフェースを実装したオブジェクト。</param>
        public void Sort(IComparer<KeyValuePair<TKey, TValue>> comparer)
        {
            this.list.Sort(comparer);
        }

        /// <summary><see cref="ListedDictionary{TKey, TValue}.Sorter"/> に設定されている <see cref="IComparer{T}"/> インターフェースを実装したオブジェクトを使用して、要素をソートします。 <see cref="Sorter"/> がnullの場合は、既定の比較を使用します。</summary>
        public void Sort()
        {
            if (this.Sorter != null)
                this.list.Sort(this.Sorter);
            else
                this.list.Sort();
        }

        /// <summary>要素をソートします。</summary>
        /// <param name="index">ソートを開始するインデックス。</param>
        /// <param name="count">ソートする範囲（要素数）。</param>
        /// <param name="comparer">要素の比較に使用する <see cref="IComparer{T}"/> インターフェースを実装したオブジェクト。</param>
        public void Sort(int index, int count, IComparer<KeyValuePair<TKey, TValue>> comparer = null)
        {
            this.list.Sort(index, count, comparer ?? this.Sorter);
        }

    }
}